package main

import (
	"bytes"
	"errors"
	"github.com/spf13/viper"
	"log"
	"os"
	"strings"
)

type config struct {
	v *viper.Viper
}

// todo https://studygolang.com/articles/14249
// 1. 读取 yaml 配置文件
func LoadConfigFromYaml(c *config) error {
	c.v = viper.New()

	// 设置配置文件的名字
	c.v.SetConfigName("config")

	//添加配置文件所在的路径,注意在Linux环境下%GOPATH要替换为$GOPATH
	//  注意：如果不用AddConfigPath去指定路径，它会在程序执行的目录去寻找config.yaml
	c.v.AddConfigPath("$GOPATH/src/")
	c.v.AddConfigPath("./")
	//设置配置文件类型
	c.v.SetConfigType("yaml")
	if err := c.v.ReadInConfig(); err != nil {
		return err
	}

	log.Printf("age: %s, name: %s \n", c.v.Get("information.age"), c.v.Get("information.name"))
	return nil
}
func initConfigFromYaml() {
	var c config
	err := LoadConfigFromYaml(&c)
	if err != nil {
		panic(err)
	}
	log.Println("init config yaml successful.")
}

// 2. 从环境变量中读取配置
// SetEnvPrefix 和 AutomaticEnv、BindEnv搭配使用很方便，比如说我们把当前程序的环境变量都设置为xx_ ，这样方便我们管理，
// 也避免和其他环境变量冲突，而在读取的时候又很方便的就可以读取。
func EnvConfigPrefix(c *config) error {
	c.v = viper.New()

	// BindEnv($1,$2)
	// 如果只传入一个参数，则会提取指定的环境变量$1，如果设置了前缀，则会自动补全 前缀_$1
	// 如果传入两个参数则不会补全前缀，直接获取第二参数中传入的环境变量$1

	os.Setenv("winter", "abc") // 设置环境变量: winter=abc
	c.v.BindEnv("f", "winter") // 绑定环境变量 f=abc (将 winter 的内容绑定到 f)
	//log.Println(os.Environ())
	log.Println(c.v.Get("f"))

	os.Setenv("LOG_LEVEL", "INFO")
	if nil == c.v.Get("LOG_LEVEL") {
		log.Printf("LOG_LEVEL is nil")
	} else {
		return errors.New("不匹配")
	}
	//必须要绑定后才能获取
	c.v.BindEnv("LOG_LEVEL")
	log.Printf("LOG_LEVEL is %s", os.Getenv("LOG_LEVEL"))

	//会获取所有的环境变量，同时如果过设置了前缀则会自动补全前缀名
	c.v.AutomaticEnv()
	//环境变量前缀大小写不区分
	os.Setenv("DEV_ADDONES", "none")
	log.Printf("DEV_ADDONES: %s", c.v.Get("dev_addones"))

	//SetEnvPrefix会设置一个环境变量的前缀名
	c.v.SetEnvPrefix("DEV")
	os.Setenv("DEV_MODE", "true")
	//此时会自动补全前缀，实际去获取的是DEV_DEV_MODE
	if nil == c.v.Get("dev_mode") {
		log.Printf("DEV_MODE is nil")
	} else {
		return errors.New("不匹配")
	}

	//此时我们直接指定了loglevel所对应的环境变量，则不会去补全前缀
	c.v.BindEnv("loglevel", "LOG_LEVEL")
	log.Printf("LOG_LEVEL: %s", c.v.Get("loglevel"))

	return nil
}

func initEnvConfigPrefix() {
	var c config
	err := EnvConfigPrefix(&c)
	if err != nil {
		panic(err)
	}
	log.Println("init env config successful.")
}

// 3. 从IO中读取配置
//由IO读取配置
func ReadConfigFormIo(c *config) error {
	c.v = viper.New()
	if f, err := os.Open("config.yaml"); err != nil {
		log.Printf("filure: %s", err.Error())
		return err
	} else {
		confLength, _ := f.Seek(0, 2)
		//注意，通常写c++的习惯害怕读取字符串的时候越界，都会多留出一个NULL在末尾，但是在这里不行，会报出如下错误：
		//While parsing config: yaml: control characters are not allowed
		//错误参考网址：https://stackoverflow.com/questions/33717799/go-yaml-control-characters-are-not-allowed-error
		configData := make([]byte, confLength)
		f.Seek(0, 0)
		f.Read(configData)
		log.Printf("%s\n", string(configData))

		c.v.SetConfigType("yaml")
		if err := c.v.ReadConfig(bytes.NewBuffer(configData)); err != nil {
			log.Fatalf(err.Error())
		}
	}
	log.Printf("age: %s, name: %s \n", c.v.Get("information.age"), c.v.Get("information.name"))

	return nil
}
func initReadConfigFromIO() {
	var c config
	err := ReadConfigFormIo(&c)
	if err != nil {
		panic(err)
	}
	log.Println("init read config successful.")
}

//4. 方便的替换符
// 我们有时候需要去替换key中的某些字符，来转化为对应的环境变脸，比如说例子中将' . '替换为'_' ，
// 由获取api.version变成了api_version，但是有一点需要注意的，SetEnvPrefix和SetEnvKeyReplacer一起用的时候可能会混淆。
func EnvConfigReplacer(c *config, setPrefix bool) error {

	c.v = viper.New()
	c.v.AutomaticEnv()
	c.v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))

	os.Setenv("API_VERSION", "v0.1.0")
	//Replacer和prefix一起使用可能会冲突,比如我下面的例子
	//因为会自动补全前缀最终由获取API_VERSION变成API_API_VERSION
	if setPrefix {
		c.v.SetEnvPrefix("api")
	}
	// true : api.api.version
	// false: api.version
	if s := c.v.Get("api.version"); s == nil {
		return errors.New("key 不存在")
	} else {
		log.Printf("%s", c.v.Get("api.version"))
	}
	return nil
}
func initEnvConfigReplacer() {
	var c config
	err := EnvConfigReplacer(&c, true)
	if err != nil {
		panic(err)
	}
	log.Println("init env config replacer successful.")
}

// 5. 别名功能
//设置重载 和别名
func SetAndAliases(c *config) error {
	c.v = viper.New()
	c.v.Set("Name", "wzp")
	c.v.RegisterAlias("id", "Name")
	c.v.Set("id", "Mr.Wang")

	//我们可以发现当别名对应的值修改之后，原本的key也发生变化
	log.Printf("id %s, name %s", c.v.Get("id"), c.v.Get("name"))
	return nil
}
func initSetAndAliases() {
	var c config
	err := SetAndAliases(&c)
	if err != nil {
		panic(err)
	}
	log.Println("init set and Aliases successful.")
}

func init() {
	//initConfigFromYaml()
	//initEnvConfigPrefix()
	//initReadConfigFromIO()
	//initEnvConfigReplacer()
	initSetAndAliases()
}

func main() {

}
